﻿using System;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using System.Reflection;
using System.Reflection.Emit;
using OperationPlugins.Compositions;

namespace OperationPlugins.Models
{
    internal static class ObjectFactory
    {
        /// <summary>
        /// Used to clone objects.
        /// </summary>
        private static ICloner Cloner = new DefaultCloner();

        /// <summary>
        /// Class default constructors.
        /// </summary>
        private static readonly ConcurrentDictionary<Type, Func<object>> Constructors = new ConcurrentDictionary<Type, Func<object>>();

        /// <summary>
        /// Type default values.
        /// </summary>
        private static readonly ConcurrentDictionary<Type, object> DefaultValues = new ConcurrentDictionary<Type, object>();

        /// <summary>
        /// Value types.
        /// </summary>
        private static readonly ConcurrentDictionary<Type, bool> ValueTypes = new ConcurrentDictionary<Type, bool>();

        /// <summary>
        /// Creates a new instance of the class.
        /// </summary>
        /// <param name="type">The type. Must be a class!</param>
        /// <returns>A new instance of the specified class.</returns>
        internal static object CreateClassInstance(Type type)
        {
            Func<object> constructor;
            if (Constructors.TryGetValue(type, out constructor) == false)
            {
                constructor = (Func<object>)GetConstructorDelegate(type, typeof(Func<object>));
                Constructors.TryAdd(type, constructor);
            }
            return constructor();
        }

        private static Delegate GetConstructorDelegate(Type type, Type delegateType)
        {
            Type[] argTypes = Type.EmptyTypes;

            ConstructorInfo constructor = type.GetConstructor(argTypes);
            if (constructor == null)
            {
                throw new InvalidProgramException(string.Format("Type '{0}' doesn't have the default constructor.", type.Name));
            }

            DynamicMethod dynamicMethod = new DynamicMethod("DM$_" + type.Name, type, argTypes, type);
            ILGenerator ilGen = dynamicMethod.GetILGenerator();
            ilGen.Emit(OpCodes.Newobj, constructor);
            ilGen.Emit(OpCodes.Ret);

            return dynamicMethod.CreateDelegate(delegateType);
        }

        /// <summary>
        /// Gets the default value of the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The default value of the specified type.</returns>
        internal static object GetDefaultValue(Type type)
        {
            object defaultValue;

            if (DefaultValues.TryGetValue(type, out defaultValue) == false)
            {
                if (IsValueType(type))
                {
                    defaultValue = Activator.CreateInstance(type);
                }
                DefaultValues.TryAdd(type, defaultValue);
            }

            return defaultValue;
        }

        /// <summary>
        /// Checks if the type is a value type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>true if the type is a value type, otherwise false.</returns>
        private static bool IsValueType(Type type)
        {
            bool b;
            if (ValueTypes.TryGetValue(type, out b) == false)
            {
                b = type.IsValueType;
                ValueTypes.TryAdd(type, b);
            }
            return b;
        }

        /// <summary>
        /// Creates a new object that is a copy of the specified object. 
        /// </summary>
        /// <param name="value">The object to be cloned. Please note, that the value can also be null!</param>
        /// <returns>A new object that is a copy of the specified object, or null value.</returns>
        internal static object Clone(object value)
        {
            return Cloner.Clone(value);
        }

        /// <summary>
        /// The default cloner.
        /// </summary>
        [Export(typeof(ICloner))]
        [DefaultPriority]
        private sealed class DefaultCloner : ICloner
        {
            object ICloner.Clone(object value)
            {
                if (value == null)
                {
                    return null;
                }

                if (IsValueType(value.GetType()))
                {
                    return value;
                }

                ICloneable cloneable = value as ICloneable;
                if (cloneable != null)
                {
                    return cloneable.Clone();
                }

                return value;
            }
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        [Export(typeof(IInitializable))]
        private sealed class Initializer : IInitializable
        {
            void IInitializable.Initialize(InitializationContext context)
            {
                Cloner = context.CompositionContainer.GetPlugin<ICloner>();
            }
        }
        
    }
}
